
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
// Layout helpers for each component positioning
import * as zrUtil from 'zrender/lib/core/util.js';
import BoundingRect from 'zrender/lib/core/BoundingRect.js';
import { parsePercent } from './number.js';
import * as formatUtil from './format.js';
var each = zrUtil.each;
/**
 * @public
 */

export var LOCATION_PARAMS = ['left', 'right', 'top', 'bottom', 'width', 'height'];
/**
 * @public
 */

export var HV_NAMES = [['width', 'left', 'right'], ['height', 'top', 'bottom']];

function boxLayout(orient, group, gap, maxWidth, maxHeight) {
	var x = 0;
	var y = 0;

	if (maxWidth == null) {
		maxWidth = Infinity;
	}

	if (maxHeight == null) {
		maxHeight = Infinity;
	}

	var currentLineMaxSize = 0;
	group.eachChild(function (child, idx) {
		var rect = child.getBoundingRect();
		var nextChild = group.childAt(idx + 1);
		var nextChildRect = nextChild && nextChild.getBoundingRect();
		var nextX;
		var nextY;

		if (orient === 'horizontal') {
			var moveX = rect.width + (nextChildRect ? -nextChildRect.x + rect.x : 0);
			nextX = x + moveX; // Wrap when width exceeds maxWidth or meet a `newline` group
			// FIXME compare before adding gap?

			if (nextX > maxWidth || child.newline) {
				x = 0;
				nextX = moveX;
				y += currentLineMaxSize + gap;
				currentLineMaxSize = rect.height;
			} else {
				// FIXME: consider rect.y is not `0`?
				currentLineMaxSize = Math.max(currentLineMaxSize, rect.height);
			}
		} else {
			var moveY = rect.height + (nextChildRect ? -nextChildRect.y + rect.y : 0);
			nextY = y + moveY; // Wrap when width exceeds maxHeight or meet a `newline` group

			if (nextY > maxHeight || child.newline) {
				x += currentLineMaxSize + gap;
				y = 0;
				nextY = moveY;
				currentLineMaxSize = rect.width;
			} else {
				currentLineMaxSize = Math.max(currentLineMaxSize, rect.width);
			}
		}

		if (child.newline) {
			return;
		}

		child.x = x;
		child.y = y;
		child.markRedraw();
		orient === 'horizontal' ? x = nextX + gap : y = nextY + gap;
	});
}
/**
 * VBox or HBox layouting
 * @param {string} orient
 * @param {module:zrender/graphic/Group} group
 * @param {number} gap
 * @param {number} [width=Infinity]
 * @param {number} [height=Infinity]
 */

export var box = boxLayout;
/**
 * VBox layouting
 * @param {module:zrender/graphic/Group} group
 * @param {number} gap
 * @param {number} [width=Infinity]
 * @param {number} [height=Infinity]
 */

export var vbox = zrUtil.curry(boxLayout, 'vertical');
/**
 * HBox layouting
 * @param {module:zrender/graphic/Group} group
 * @param {number} gap
 * @param {number} [width=Infinity]
 * @param {number} [height=Infinity]
 */

export var hbox = zrUtil.curry(boxLayout, 'horizontal');
/**
 * If x or x2 is not specified or 'center' 'left' 'right',
 * the width would be as long as possible.
 * If y or y2 is not specified or 'middle' 'top' 'bottom',
 * the height would be as long as possible.
 */

export function getAvailableSize(positionInfo, containerRect, margin) {
	var containerWidth = containerRect.width;
	var containerHeight = containerRect.height;
	var x = parsePercent(positionInfo.left, containerWidth);
	var y = parsePercent(positionInfo.top, containerHeight);
	var x2 = parsePercent(positionInfo.right, containerWidth);
	var y2 = parsePercent(positionInfo.bottom, containerHeight);
	(isNaN(x) || isNaN(parseFloat(positionInfo.left))) && (x = 0);
	(isNaN(x2) || isNaN(parseFloat(positionInfo.right))) && (x2 = containerWidth);
	(isNaN(y) || isNaN(parseFloat(positionInfo.top))) && (y = 0);
	(isNaN(y2) || isNaN(parseFloat(positionInfo.bottom))) && (y2 = containerHeight);
	margin = formatUtil.normalizeCssArray(margin || 0);
	return {
		width: Math.max(x2 - x - margin[1] - margin[3], 0),
		height: Math.max(y2 - y - margin[0] - margin[2], 0)
	};
}
/**
 * Parse position info.
 */

export function getLayoutRect(positionInfo, containerRect, margin) {
	margin = formatUtil.normalizeCssArray(margin || 0);
	var containerWidth = containerRect.width;
	var containerHeight = containerRect.height;
	var left = parsePercent(positionInfo.left, containerWidth);
	var top = parsePercent(positionInfo.top, containerHeight);
	var right = parsePercent(positionInfo.right, containerWidth);
	var bottom = parsePercent(positionInfo.bottom, containerHeight);
	var width = parsePercent(positionInfo.width, containerWidth);
	var height = parsePercent(positionInfo.height, containerHeight);
	var verticalMargin = margin[2] + margin[0];
	var horizontalMargin = margin[1] + margin[3];
	var aspect = positionInfo.aspect; // If width is not specified, calculate width from left and right

	if (isNaN(width)) {
		width = containerWidth - right - horizontalMargin - left;
	}

	if (isNaN(height)) {
		height = containerHeight - bottom - verticalMargin - top;
	}

	if (aspect != null) {
		// If width and height are not given
		// 1. Graph should not exceeds the container
		// 2. Aspect must be keeped
		// 3. Graph should take the space as more as possible
		// FIXME
		// Margin is not considered, because there is no case that both
		// using margin and aspect so far.
		if (isNaN(width) && isNaN(height)) {
			if (aspect > containerWidth / containerHeight) {
				width = containerWidth * 0.8;
			} else {
				height = containerHeight * 0.8;
			}
		} // Calculate width or height with given aspect

		if (isNaN(width)) {
			width = aspect * height;
		}

		if (isNaN(height)) {
			height = width / aspect;
		}
	} // If left is not specified, calculate left from right and width

	if (isNaN(left)) {
		left = containerWidth - right - width - horizontalMargin;
	}

	if (isNaN(top)) {
		top = containerHeight - bottom - height - verticalMargin;
	} // Align left and top

	switch (positionInfo.left || positionInfo.right) {
	case 'center':
		left = containerWidth / 2 - width / 2 - margin[3];
		break;

	case 'right':
		left = containerWidth - width - horizontalMargin;
		break;
	}

	switch (positionInfo.top || positionInfo.bottom) {
	case 'middle':
	case 'center':
		top = containerHeight / 2 - height / 2 - margin[0];
		break;

	case 'bottom':
		top = containerHeight - height - verticalMargin;
		break;
	} // If something is wrong and left, top, width, height are calculated as NaN

	left = left || 0;
	top = top || 0;

	if (isNaN(width)) {
		// Width may be NaN if only one value is given except width
		width = containerWidth - horizontalMargin - left - (right || 0);
	}

	if (isNaN(height)) {
		// Height may be NaN if only one value is given except height
		height = containerHeight - verticalMargin - top - (bottom || 0);
	}

	var rect = new BoundingRect(left + margin[3], top + margin[0], width, height);
	rect.margin = margin;
	return rect;
}
/**
 * Position a zr element in viewport
 *  Group position is specified by either
 *  {left, top}, {right, bottom}
 *  If all properties exists, right and bottom will be igonred.
 *
 * Logic:
 *     1. Scale (against origin point in parent coord)
 *     2. Rotate (against origin point in parent coord)
 *     3. Translate (with el.position by this method)
 * So this method only fixes the last step 'Translate', which does not affect
 * scaling and rotating.
 *
 * If be called repeatedly with the same input el, the same result will be gotten.
 *
 * Return true if the layout happened.
 *
 * @param el Should have `getBoundingRect` method.
 * @param positionInfo
 * @param positionInfo.left
 * @param positionInfo.top
 * @param positionInfo.right
 * @param positionInfo.bottom
 * @param positionInfo.width Only for opt.boundingModel: 'raw'
 * @param positionInfo.height Only for opt.boundingModel: 'raw'
 * @param containerRect
 * @param margin
 * @param opt
 * @param opt.hv Only horizontal or only vertical. Default to be [1, 1]
 * @param opt.boundingMode
 *        Specify how to calculate boundingRect when locating.
 *        'all': Position the boundingRect that is transformed and uioned
 *               both itself and its descendants.
 *               This mode simplies confine the elements in the bounding
 *               of their container (e.g., using 'right: 0').
 *        'raw': Position the boundingRect that is not transformed and only itself.
 *               This mode is useful when you want a element can overflow its
 *               container. (Consider a rotated circle needs to be located in a corner.)
 *               In this mode positionInfo.width/height can only be number.
 */

export function positionElement(el, positionInfo, containerRect, margin, opt, out) {
	var h = !opt || !opt.hv || opt.hv[0];
	var v = !opt || !opt.hv || opt.hv[1];
	var boundingMode = opt && opt.boundingMode || 'all';
	out = out || el;
	out.x = el.x;
	out.y = el.y;

	if (!h && !v) {
		return false;
	}

	var rect;

	if (boundingMode === 'raw') {
		rect = el.type === 'group' ? new BoundingRect(0, 0, +positionInfo.width || 0, +positionInfo.height || 0) : el.getBoundingRect();
	} else {
		rect = el.getBoundingRect();

		if (el.needLocalTransform()) {
			var transform = el.getLocalTransform(); // Notice: raw rect may be inner object of el,
			// which should not be modified.

			rect = rect.clone();
			rect.applyTransform(transform);
		}
	} // The real width and height can not be specified but calculated by the given el.

	var layoutRect = getLayoutRect(zrUtil.defaults({
		width: rect.width,
		height: rect.height
	}, positionInfo), containerRect, margin); // Because 'tranlate' is the last step in transform
	// (see zrender/core/Transformable#getLocalTransform),
	// we can just only modify el.position to get final result.

	var dx = h ? layoutRect.x - rect.x : 0;
	var dy = v ? layoutRect.y - rect.y : 0;

	if (boundingMode === 'raw') {
		out.x = dx;
		out.y = dy;
	} else {
		out.x += dx;
		out.y += dy;
	}

	if (out === el) {
		el.markRedraw();
	}

	return true;
}
/**
 * @param option Contains some of the properties in HV_NAMES.
 * @param hvIdx 0: horizontal; 1: vertical.
 */

export function sizeCalculable(option, hvIdx) {
	return option[HV_NAMES[hvIdx][0]] != null || option[HV_NAMES[hvIdx][1]] != null && option[HV_NAMES[hvIdx][2]] != null;
}
export function fetchLayoutMode(ins) {
	var layoutMode = ins.layoutMode || ins.constructor.layoutMode;
	return zrUtil.isObject(layoutMode) ? layoutMode : layoutMode ? {
		type: layoutMode
	} : null;
}
/**
 * Consider Case:
 * When default option has {left: 0, width: 100}, and we set {right: 0}
 * through setOption or media query, using normal zrUtil.merge will cause
 * {right: 0} does not take effect.
 *
 * @example
 * ComponentModel.extend({
 *     init: function () {
 *         ...
 *         let inputPositionParams = layout.getLayoutParams(option);
 *         this.mergeOption(inputPositionParams);
 *     },
 *     mergeOption: function (newOption) {
 *         newOption && zrUtil.merge(thisOption, newOption, true);
 *         layout.mergeLayoutParam(thisOption, newOption);
 *     }
 * });
 *
 * @param targetOption
 * @param newOption
 * @param opt
 */

export function mergeLayoutParam(targetOption, newOption, opt) {
	var ignoreSize = opt && opt.ignoreSize;
	!zrUtil.isArray(ignoreSize) && (ignoreSize = [ignoreSize, ignoreSize]);
	var hResult = merge(HV_NAMES[0], 0);
	var vResult = merge(HV_NAMES[1], 1);
	copy(HV_NAMES[0], targetOption, hResult);
	copy(HV_NAMES[1], targetOption, vResult);

	function merge(names, hvIdx) {
		var newParams = {};
		var newValueCount = 0;
		var merged = {};
		var mergedValueCount = 0;
		var enoughParamNumber = 2;
		each(names, function (name) {
			merged[name] = targetOption[name];
		});
		each(names, function (name) {
			// Consider case: newOption.width is null, which is
			// set by user for removing width setting.
			hasProp(newOption, name) && (newParams[name] = merged[name] = newOption[name]);
			hasValue(newParams, name) && newValueCount++;
			hasValue(merged, name) && mergedValueCount++;
		});

		if (ignoreSize[hvIdx]) {
			// Only one of left/right is premitted to exist.
			if (hasValue(newOption, names[1])) {
				merged[names[2]] = null;
			} else if (hasValue(newOption, names[2])) {
				merged[names[1]] = null;
			}

			return merged;
		} // Case: newOption: {width: ..., right: ...},
		// or targetOption: {right: ...} and newOption: {width: ...},
		// There is no conflict when merged only has params count
		// little than enoughParamNumber.

		if (mergedValueCount === enoughParamNumber || !newValueCount) {
			return merged;
		} // Case: newOption: {width: ..., right: ...},
		// Than we can make sure user only want those two, and ignore
		// all origin params in targetOption.
		else if (newValueCount >= enoughParamNumber) {
			return newParams;
		} else {
			// Chose another param from targetOption by priority.
			for (var i = 0; i < names.length; i++) {
				var name_1 = names[i];

				if (!hasProp(newParams, name_1) && hasProp(targetOption, name_1)) {
					newParams[name_1] = targetOption[name_1];
					break;
				}
			}

			return newParams;
		}
	}

	function hasProp(obj, name) {
		return obj.hasOwnProperty(name);
	}

	function hasValue(obj, name) {
		return obj[name] != null && obj[name] !== 'auto';
	}

	function copy(names, target, source) {
		each(names, function (name) {
			target[name] = source[name];
		});
	}
}
/**
 * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
 */

export function getLayoutParams(source) {
	return copyLayoutParams({}, source);
}
/**
 * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object.
 * @param {Object} source
 * @return {Object} Result contains those props.
 */

export function copyLayoutParams(target, source) {
	source && target && each(LOCATION_PARAMS, function (name) {
		source.hasOwnProperty(name) && (target[name] = source[name]);
	});
	return target;
}